#include "xmiimporter.h"

#include "../documents/swpprocessdocument.h"
#include "../documents/swpresourcedocument.h"
#include "../editors/items/editoritemcommon.h"
#include "../editors/items/swpaggregationgraphicitem.h"
#include "../editors/items/swpartifactgraphicitem.h"
#include "../editors/items/swpassociationgraphicitem.h"
#include "../editors/items/swpcompositiongraphicitem.h"
#include "../editors/items/swpgeneralizationgraphicitem.h"
#include "../editors/items/swprealizationgraphicitem.h"
#include "../editors/items/swpactivityconnectorgraphicitem.h"
#include "../editors/items/swpstartpointgraphicitem.h"
#include "../editors/items/swpendpointgraphicitem.h"
#include "../editors/items/swpmergegraphicitem.h"
#include "../editors/items/swpdecisiongraphicitem.h"
#include "../editors/items/swpsplitgraphicitem.h"
#include "../editors/items/swpjoingraphicitem.h"
#include "../editors/items/swpactivitygraphicitem.h"

#include "documentmanager.h"

#include <coremimetypes.h>
#include <mimetypemanager.h>

#include <QtCore/QFile>
#include <QtCore/QStringList>
#include <QtCore/QUrl>
#include <QtXmlPatterns/QXmlResultItems>
#include <QtXmlPatterns/QXmlQuery>

using namespace core;
using namespace swp;

typedef QSharedPointer<IDocument> Document;
typedef QSharedPointer<IContainerDocument> ContainerDocument;

XmiImporter* xmiImporter = 0;

XmiImporter* XmiImporter::getInstance()
{
    if (xmiImporter == 0)
    {
        xmiImporter = new XmiImporter();
    }

    return xmiImporter;
}

void XmiImporter::destroy()
{
    delete xmiImporter;
    xmiImporter = 0;
}

void XmiImporter::import(
    const QSharedPointer<core::IContainerDocument>& project,
    const QUrl& destination)
{
    QFile* file = new QFile(destination.toLocalFile());

    if (file->open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QXmlQuery query1;
        query1.setFocus(file);
        query1.setQuery("declare namespace xmi = \"http://schema.omg.org/spec/XMI/2.1\"; "
            "declare namespace uml = \"http://schema.omg.org/spec/UML/2.0\"; "
            "//uml:Model/ownedMember[@xmi:type!='uml:Stereotype']");

        QXmlResultItems modelElements;

        query1.evaluateTo(&modelElements);

        QXmlItem modelElement = modelElements.next();

        while (!modelElement.isNull())
        {
            ModelElement me;
            me.Name = attributeValue(query1, "name", modelElement);
            me.Id = attributeValue(query1, "xmi:id", modelElement);
            me.Type = attributeValue(query1, "xmi:type", modelElement);

            if(me.Type == "uml:Association" || me.Type == "uml:Realization")
            {
                parseAssociation(query1, modelElement, me);
            }
            else if (me.Type == "uml:Activity")
            {
                if (me.Id == "activity_id")
                {
                    QXmlResultItems nodes;

                    childElement(query1, "node", modelElement,
                        nodes);

                    QXmlItem node = nodes.next();

                    while (!node.isNull())
                    {
                        ModelElement mne;
                        mne.Name = attributeValue(query1, "name", node);
                        mne.Id = attributeValue(query1, "xmi:id", node);
                        mne.Type = attributeValue(query1, "kind", node);
                        if (mne.Type.isEmpty())
                        {
                            mne.Type = attributeValue(query1, "xmi:type",
                                node);
                        }

                        if (mne.Type == "junction")
                        {
                            if (mne.Name.endsWith("Node"))
                            {
                                mne.Type = QString("uml:%1").arg(mne.Name);
                            }
                        }
                        else if (mne.Type.contains("swimlane",
                            Qt::CaseInsensitive))
                        {
                            QXmlResultItems sNodes;

                            childElement(query1, "ownedMember", node,
                                sNodes);

                            QXmlItem sNode = sNodes.next();

                            while (!sNode.isNull())
                            {
                                ModelElement smne;
                                smne.Name = attributeValue(query1, "name",
                                    sNode);
                                smne.Id = attributeValue(query1, "xmi:id",
                                    sNode);
                                smne.Type = attributeValue(query1, "kind",
                                    sNode);
                                if (smne.Type.isEmpty())
                                {
                                    smne.Type = attributeValue(query1,
                                        "xmi:type", sNode);
                                }

                                if (smne.Type == "junction")
                                {
                                    if (smne.Name.endsWith("Node"))
                                    {
                                        smne.Type = QString("uml:%1").arg(
                                            smne.Name);
                                    }
                                }
                                else if (!smne.Type.isEmpty() &&
                                    !smne.Type.contains("Activity",
                                        Qt::CaseInsensitive))
                                {
                                    smne.Type = QString("uml:%1").arg(
                                        smne.Type.at(0).toUpper() +
                                        smne.Type.mid(1));
                                }
                                else if (!smne.Type.contains("Activity",
                                    Qt::CaseInsensitive))
                                {
                                    smne.Type = "uml:Invalid";
                                }

                                parseControlFlows(query1, sNode, smne);

                                m_modelElements[smne.Id] = smne;

                                sNode = sNodes.next();
                            }
                        }
                        else if (!mne.Type.isEmpty() &&
                            !mne.Type.contains("Activity",
                                Qt::CaseInsensitive))
                        {
                            mne.Type = QString("uml:%1").arg(
                                mne.Type.at(0).toUpper() + mne.Type.mid(1));
                        }
                        else if (!mne.Type.contains("Activity",
                            Qt::CaseInsensitive))
                        {
                            mne.Type = "uml:Invalid";
                        }

                        m_modelElements[mne.Id] = mne;

                        node = nodes.next();
                    }

                    parseControlFlows(query1, modelElement, me);
                }
                else
                {
                    parseControlFlows(query1, modelElement, me);

                    m_modelElements[me.Id] = me;
                }
            }
            else
            {
                if (me.Type == "uml:Package")
                {
                    QXmlResultItems subElements;

                    childElement(query1, "ownedMember", modelElement,
                        subElements);

                    QXmlItem subElement = subElements.next();

                    while (!subElement.isNull())
                    {
                        ModelElement sme;
                        sme.Name = attributeValue(query1, "name", subElement);
                        sme.Id = attributeValue(query1, "xmi:id", subElement);
                        sme.Type = attributeValue(query1, "xmi:type",
                            subElement);

                        if(sme.Type == "uml:Association" ||
                            sme.Type == "uml:Realization")
                        {
                            parseAssociation(query1, subElement, sme);
                        }
                        else
                        {
                            parseGeneralizations(query1, subElement, sme);

                            m_modelElements[sme.Id] = sme;
                        }

                        subElement = subElements.next();
                    }
                }
                else
                {
                    parseGeneralizations(query1, modelElement, me);

                    m_modelElements[me.Id] = me;
                }
            }

            modelElement = modelElements.next();
        }
    }
    file->close();

    if (file->open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QXmlQuery query2;
        query2.setFocus(file);
        query2.setQuery("declare namespace xmi = \"http://schema.omg.org/spec/XMI/2.1\"; "
           "declare namespace uml = \"http://schema.omg.org/spec/UML/2.0\"; "
           "//uml:DiagramElement");

        QXmlResultItems diagramElements;

        query2.evaluateTo(&diagramElements);

        QXmlItem diagramElement = diagramElements.next();

        while (!diagramElement.isNull())
        {
            DiagramElement de;
            de.Shape = attributeValue(query2, "preferredShapeType",
                diagramElement);
            de.Id = attributeValue(query2, "xmi:id", diagramElement);
            de.FId = attributeValue(query2, "subject", diagramElement);
            de.Geometry = attributeValue(query2, "geometry",diagramElement);

            m_diagramElements[de.Id] = de;

            diagramElement = diagramElements.next();
        }
    }
    file->close();

    if (file->open(QIODevice::ReadOnly | QIODevice::Text))
    {
        QXmlQuery query3;
        query3.setFocus(file);
        query3.setQuery("declare namespace xmi = \"http://schema.omg.org/spec/XMI/2.1\"; "
            "declare namespace uml = \"http://schema.omg.org/spec/UML/2.0\"; "
            "//uml:Diagram[@diagramType='ClassDiagram'] | "
            "//uml:Diagram[@diagramType='ActivityDiagram']");

        QXmlResultItems diagrams;

        query3.evaluateTo(&diagrams);

        QXmlItem diagram = diagrams.next();

        while (!diagram.isNull())
        {
            Diagram d;
            d.Name = attributeValue(query3, "name",
                diagram);
            d.Id = attributeValue(query3, "xmi:id",
                diagram);
            d.DiagramType = attributeValue(query3, "diagramType",
                diagram);

            QXmlResultItems diagramElements;

            childElement(query3, "uml:Diagram.element/uml:DiagramElement",
                diagram,
                diagramElements);

            QXmlItem diagramElement = diagramElements.next();

            while (!diagramElement.isNull())
            {
                const QString id = attributeValue(query3, "xmi:id",
                    diagramElement);

                if (!id.isEmpty())
                {
                    d.Elements.append(id);
                }

                diagramElement = diagramElements.next();
            }

            m_diagrams[d.Id] = d;

            diagram = diagrams.next();
        }
    }
    file->close();

    createDocuments(project);

    m_connectionElements.clear();
    m_modelElements.clear();
    m_diagramElements.clear();
    m_diagrams.clear();
    m_itemMapping.clear();
}

XmiImporter::XmiImporter() : QObject(),
    m_connectionElements(),
    m_modelElements(),
    m_diagramElements(),
    m_diagrams(),
    m_itemMapping()
{

}

XmiImporter::~XmiImporter()
{

}

QString XmiImporter::attributeValue(QXmlQuery& query,
    const QString& attribute, const QXmlItem& element) const
{
    QString ret;

    query.setFocus(element);
    query.setQuery(QString("declare namespace xmi = \"http://schema.omg.org/spec/XMI/2.1\"; "
        "declare namespace uml = \"http://schema.omg.org/spec/UML/2.0\"; "
        "./@%1/data(.)").arg(attribute));

    QXmlResultItems items;

    query.evaluateTo(&items);

    QXmlItem item = items.next();

    if (!item.isNull() && item.isAtomicValue())
    {
        QVariant v = item.toAtomicValue();

        switch (v.type()) {
            case QVariant::String:
            {
                ret = v.toString();

                QRegExp rx("(\\\\u[0-9a-fA-F]{4})");
                int pos = 0;
                while ((pos = rx.indexIn(ret, pos)) != -1) {
                    ret.replace(pos++, 6, QChar(rx.cap(1).right(4).toUShort(
                        0, 16)));
                }
                break;
            }
            default:
                // do nothing
                break;
        }
    }

    return ret;
}

QString XmiImporter::value(QXmlQuery& query, const QXmlItem& element) const
{
    QString ret;

    query.setFocus(element);
    query.setQuery("declare namespace xmi = \"http://schema.omg.org/spec/XMI/2.1\"; "
        "declare namespace uml = \"http://schema.omg.org/spec/UML/2.0\"; "
        "./text()");

    QXmlResultItems items;

    query.evaluateTo(&items);

    QXmlItem item = items.next();

    if (!item.isNull() && item.isAtomicValue())
    {
        QVariant v = item.toAtomicValue();

        switch (v.type()) {
            case QVariant::String:
                ret = v.toString();
                break;
            default:
                // do nothing
                break;
        }
    }

    return ret;
}

void XmiImporter::childElement(QXmlQuery& query, const QString& name,
    const QXmlItem& element, QXmlResultItems& child) const
{
    query.setFocus(element);
    query.setQuery(QString("declare namespace xmi = \"http://schema.omg.org/spec/XMI/2.1\"; "
        "declare namespace uml = \"http://schema.omg.org/spec/UML/2.0\"; "
        "./%1").arg(name));

    query.evaluateTo(&child);
}

void XmiImporter::createDocuments(
    const QSharedPointer<IContainerDocument>& project)
{
    foreach (const Diagram& diagram, m_diagrams.values())
    {
        if (diagram.DiagramType == "ActivityDiagram")
        {
            SwpProcessDocument* process =
                new SwpProcessDocument(diagram.Name,
                    MimeTypeManager::getInstance()->mimeType(
                        ACTIVITY_DIAGRAM_MIMETYPE), project, false);

            QSharedPointer<IDocument> _process =
                QSharedPointer<IDocument>(process);

            fillDocuments(process->data(), diagram);

            DocumentManager::getInstance()->addDocument(_process->container(),
                _process, 0);
        }
        else if (diagram.DiagramType == "ClassDiagram")
        {
            SwpResourceDocument* resource =
                new SwpResourceDocument(diagram.Name,
                    MimeTypeManager::getInstance()->mimeType(
                        CLASS_DIAGRAM_MIMETYPE), project, false);

            QSharedPointer<IDocument> _resource =
                QSharedPointer<IDocument>(resource);

            fillDocuments(resource->data(), diagram);

            DocumentManager::getInstance()->addDocument(_resource->container(),
                _resource, 0);
        }
    }
}

void XmiImporter::fillDocuments(IDocumentData* documentData,
    const Diagram& diagram)
{
    GraphicData* data = dynamic_cast<GraphicData* >(documentData);
    data->setGraphicPalette(0);

    Q_ASSERT(data != 0);

    data->setIgnoreSnapFlag(true);

    QList<DiagramElement> connections;

    foreach (const QString& id, diagram.Elements)
    {
        if (m_diagramElements.contains(id))
        {
            const DiagramElement& el = m_diagramElements[id];

            if (m_modelElements.contains(el.FId))
            {
                const ModelElement& mel = m_modelElements[el.FId];

                IGraphicEditorItem* item = createEditorItem(mel, el);

                if (item != 0)
                {
                    item->setParentDocumentData(data);
                    data->addGraphicItem(item);

                    m_itemMapping[mel.Id] = item;
                }
            }
            else if (m_connectionElements.contains(el.FId))
            {
                connections.append(el);
            }
        }
    }

    foreach (const DiagramElement& el, connections)
    {
        if (m_connectionElements.contains(el.FId))
        {
            const ConnectionElement& mel = m_connectionElements[el.FId];

            createConnection(mel, el, data);
        }
    }

    data->setIgnoreSnapFlag(false);
}

IGraphicEditorItem* XmiImporter::createEditorItem(const ModelElement& mel,
    const DiagramElement& el) const
{
    IGraphicEditorItem* ret = 0;

    const QStringList parts = el.Geometry.split(",", QString::SkipEmptyParts);
    QPointF pos;

    if (parts.size() > 1)
    {
        pos.rx() = parts[0].toInt();
        pos.ry() = parts[1].toInt();
    }

    if (mel.Type == "uml:Interface")
    {
        SwpArtifactGraphicItem* item = new SwpArtifactGraphicItem(mel.Name);
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:Class")
    {
        SwpArtifactGraphicItem* item = new SwpArtifactGraphicItem(mel.Name);
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:Initial")
    {
        SwpStartPointGraphicItem* item = new SwpStartPointGraphicItem();
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:Final")
    {
        SwpEndPointGraphicItem* item  = new SwpEndPointGraphicItem();
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:MergeNode" || (mel.Type == "junction" &&
        el.Shape == "MergeNode"))
    {
        SwpMergeGraphicItem* item = new SwpMergeGraphicItem();
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:DecisionNode" || (mel.Type == "junction" &&
        el.Shape == "DecisionNode"))
    {
        SwpDecisionGraphicItem* item = new SwpDecisionGraphicItem();
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:Fork" || mel.Type == "uml:ForkNode" ||
        (mel.Type == "junction" && el.Shape == "ForkNode"))
    {
        SwpSplitGraphicItem* item = new SwpSplitGraphicItem();
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:Join" || mel.Type == "uml:JoinNode" ||
        (mel.Type == "junction" && el.Shape == "JoinNode"))
    {
        SwpJoinGraphicItem* item = new SwpJoinGraphicItem();
        item->setPos(pos);

        ret = item;
    }
    else if (mel.Type == "uml:Activity")
    {
        SwpActivityGraphicItem* item = new SwpActivityGraphicItem(mel.Name);
        item->setPos(pos);

        ret = item;
    }

    return ret;
}

void XmiImporter::createConnection(const ConnectionElement& mel,
    const DiagramElement& el, GraphicData* data) const
{
    if (m_itemMapping.contains(mel.FromId) &&
        m_itemMapping.contains(mel.ToId))
    {
        uint conId = CONNECTION_NOT_VALID;
        QUuid connector;

        const QStringList points = el.Geometry.split(";",
            QString::SkipEmptyParts);

        if (points.size() > 1)
        {
            const QStringList first = points.first().split(",",
                QString::SkipEmptyParts);
            const QStringList second = points.last().split(",",
                QString::SkipEmptyParts);

            if (first.size() == 2 && second.size() == 2)
            {
                const QPointF ep(first.first().toInt(), first.last().toInt());
                const QPointF sp(second.first().toInt(),
                    second.last().toInt());

                if (mel.Type == "uml:Generalization")
                {
                    conId = CONNECTION_GENERALIZATION;

                    SwpGeneralizationGraphicItem* item =
                        new SwpGeneralizationGraphicItem();
                    item->setStartPoint(sp);
                    item->setEndPoint(ep);

                    data->addGraphicItem(item);

                    connector = item->id();
                }
                else if (mel.Type == "uml:Realization")
                {
                    conId = CONNECTION_REALIZATION;

                    SwpRealizationGraphicItem* item =
                        new SwpRealizationGraphicItem();
                    item->setStartPoint(sp);
                    item->setEndPoint(ep);

                    data->addGraphicItem(item);

                    connector = item->id();
                }
                else if (mel.Type == "uml:Association")
                {
                    conId = CONNECTION_ASSOCIATION;

                    SwpAssociationGraphicItem* item =
                        new SwpAssociationGraphicItem();
                    item->setStartPoint(sp);
                    item->setEndPoint(ep);

                    data->addGraphicItem(item);

                    connector = item->id();
                }
                else if (mel.Type == "uml:Composition")
                {
                    conId = CONNECTION_COMPOSITION;

                    SwpCompositionGraphicItem* item =
                        new SwpCompositionGraphicItem();
                    item->setStartPoint(sp);
                    item->setEndPoint(ep);

                    data->addGraphicItem(item);

                    connector = item->id();
                }
                else if (mel.Type == "uml:Aggregation")
                {
                    conId = CONNECTION_AGGREGATION;

                    SwpAggregationGraphicItem* item =
                        new SwpAggregationGraphicItem();
                    item->setStartPoint(sp);
                    item->setEndPoint(ep);

                    data->addGraphicItem(item);

                    connector = item->id();
                }
                else if (mel.Type == "uml:ControlFlow")
                {
                    conId = CONNECTION_ACTIVITY;

                    SwpActivityConnectorGraphicItem* item =
                        new SwpActivityConnectorGraphicItem();
                    item->setStartPoint(ep);
                    item->setEndPoint(sp);

                    data->addGraphicItem(item);

                    connector = item->id();
                }
            }
        }

        if (conId != CONNECTION_NOT_VALID)
        {
            IGraphicEditorItem* from = m_itemMapping[mel.FromId];
            IGraphicEditorItem* to = m_itemMapping[mel.ToId];

            data->makeConnection(conId, connector, from->id(), to->id());

            from->arrangeConnectorsRequest();
            to->arrangeConnectorsRequest();
        }
    }
}

void XmiImporter::parseGeneralizations(QXmlQuery& query,
    const QXmlItem& parent, const ModelElement& me)
{
    QXmlResultItems generalizations;

    childElement(query, "generalization", parent,
        generalizations);

    QXmlItem generalization = generalizations.next();

    while (!generalization.isNull())
    {
        ConnectionElement ce;
        ce.Id = attributeValue(query, "xmi:id",
            generalization);
        ce.Type = attributeValue(query, "xmi:type",
            generalization);
        ce.ToId = attributeValue(query, "general",
            generalization);
        ce.FromId = me.Id;

        m_connectionElements[ce.Id] = ce;

        generalization = generalizations.next();
    }
}

void XmiImporter::parseAssociation(QXmlQuery& query, const QXmlItem& element,
    const ModelElement& me)
{
    ConnectionElement ce;
    ce.Id = me.Id;
    ce.Type = me.Type;

    if (ce.Type == "uml:Realization")
    {
        ce.ToId = attributeValue(query, "supplier",
            element);
        ce.FromId = attributeValue(query, "client",
            element);
    }
    else
    {
        QXmlResultItems connections;

        childElement(query, "ownedEnd", element,
            connections);

        QXmlItem connection = connections.next();

        QString specializedType;

        if (!connection.isNull())
        {
            ce.ToId = attributeValue(query, "type",
                connection);
            specializedType = attributeValue(query,
                "aggregation", connection);
        }

        bool specializedTypeSet = false;
        if (specializedType != "none" &&
            !specializedType.isEmpty())
        {
            ce.Type = specializedType == "aggregate" ?
                "uml:Aggregation" : "uml:Composition";
            specializedTypeSet = true;
        }

        connection = connections.next();

        if (!connection.isNull())
        {
            ce.FromId = attributeValue(query, "type",
                connection);
            specializedType = attributeValue(query,
                "aggregation", connection);
        }

        if (!specializedTypeSet && (specializedType != "none" &&
            !specializedType.isEmpty()))
        {
            ce.Type = specializedType == "aggregate" ?
                "uml:Aggregation" : "uml:Composition";
        }
    }

    m_connectionElements[ce.Id] = ce;
}

void XmiImporter::parseControlFlows(QXmlQuery& query, const QXmlItem& parent,
    const ModelElement& me)
{
    QXmlResultItems controlFlows;

    childElement(query, "edge", parent,
        controlFlows);

    QXmlItem controlFlow = controlFlows.next();

    while (!controlFlow.isNull())
    {
        ConnectionElement ce;
        ce.Id = attributeValue(query, "xmi:id",
            controlFlow);
        ce.Type = "uml:ControlFlow";
        ce.FromId = attributeValue(query, "source",
            controlFlow);
        ce.ToId = attributeValue(query, "target",
            controlFlow);

        m_connectionElements[ce.Id] = ce;

        controlFlow = controlFlows.next();
    }
}
